// hwintext.h
#pragma once

#ifndef __HWINTEXT_H__
#define __HWINTEXT_H__

#include "hwinobject.h"
#include "hwinstring.h"
#include "hwinexception.h"
#include "hwintypes.h"

namespace harlinn
{
    namespace windows
    {
        namespace text
        {
            class StringBuilder
            {
                static const unsigned DataBlockTextSize = (2048 - sizeof(wchar_t*))/ sizeof(wchar_t);

                struct DataBlock
                {
                    DataBlock* next;
                    wchar_t text[DataBlockTextSize];

                    DataBlock()
                        : next(0)
                    {}

                    ~DataBlock()
                    {
                        if(next)
                        {
                            delete next;
                        }
                    }

                };

                struct Data 
                {
                    long referenceCount;
                    size_t capacity;
                    size_t length;
                    DataBlock* start;
                    DataBlock* end;
                    DataBlock* current;
                    size_t currentUsed;

                    Data(size_t newCapacity)
                        : referenceCount(1),
                        capacity(0),
                        length(0),
                        start(0),
                        end(0),
                        current(0),
                        currentUsed(0)
                    {
                        SetCapacity(newCapacity);
                    }

                    ~Data()
                    {
                        if(start)
                        {
                            delete start;
                        }
                    }

                    Data& Reset()
                    {
                        referenceCount = 1;
                        length = 0;
                        current = start;
                        currentUsed = 0;
                        return *this;
                    }


                    Data& AddDataBlock()
                    {
                        DataBlock* newBlock = new DataBlock();
                        if(newBlock)
                        {
                            if(end)
                            {
                                end->next = newBlock;
                                end = newBlock;
                            }
                            else
                            {
                                start = newBlock;
                                current = newBlock;
                                end = newBlock;
                            }
                            capacity += DataBlockTextSize;
                        }
                        else
                        {
                            throw OutOfMemoryException();
                        }
                        return *this;
                    }

                    size_t Capacity() const
                    {
                        return capacity;
                    }


                    Data& SetCapacity(size_t newCapacity)
                    {
                        while(capacity < newCapacity)
                        {
                            AddDataBlock();
                        }
                        return *this;
                    }


                    Data& Append(const wchar_t* text, size_t textLength)
                    {
                        size_t minRequiredCapacity = length+textLength;
                        if(minRequiredCapacity > capacity)
                        {
                            SetCapacity(minRequiredCapacity);
                        }
                        
                        if(currentUsed < (DataBlockTextSize))
                        {
                            size_t copyLength = textLength;
                            if(copyLength > (DataBlockTextSize - currentUsed) )
                            {
                                copyLength = DataBlockTextSize - currentUsed;
                            }
                            wmemcpy( current->text + currentUsed, text, copyLength  );
                            length += copyLength;
                            text += copyLength;
                            textLength -= copyLength;
                            currentUsed += copyLength;
                        }
                        while(textLength)
                        {
                            current = current->next;
                            size_t copyLength = textLength;
                            if(copyLength > DataBlockTextSize)
                            {
                                currentUsed = DataBlockTextSize;
                                copyLength = DataBlockTextSize;
                            }
                            else
                            {
                                currentUsed = copyLength;
                            }

                            wmemcpy( current->text, text, copyLength  );
                            length += copyLength;
                            text += copyLength;
                            textLength -= copyLength;
                        }
                        return *this;
                    }

                    Data& Append(const wchar_t c)
                    {
                        size_t minRequiredCapacity = length+1;
                        if(minRequiredCapacity > capacity)
                        {
                            SetCapacity(minRequiredCapacity);
                        }
                        if(currentUsed >= DataBlockTextSize)
                        {
                            current = current->next;
                            currentUsed = 0;
                        }
                        current->text[currentUsed] = c;
                        length++;
                        currentUsed++;
                        return *this;
                    }


                    String ToString() const
                    {
                        String result;
                        if(length)
                        {
                            result.SetLength(length);
                            wchar_t* dest = result.c_str();
                            DataBlock* block = start;
                            while(block != current)
                            {
                                wmemcpy(dest,block->text,DataBlockTextSize);
                                block = block->next;
                            }
                            wmemcpy(dest,block->text,currentUsed);
                        }
                        return result;
                    }

                    long AddRef()
                    {
                        return InterlockedIncrement(&referenceCount);
                    }

                    long Release()
                    {
                        return InterlockedDecrement(&referenceCount);
                    }

                };

                class DataManager
                {
                    static const unsigned cacheSize = 5;
                    CRITICAL_SECTION mutex;
                    Data* cache[cacheSize];
                public:
                    HWIN_EXPORT DataManager();
                    HWIN_EXPORT ~DataManager();
                    HWIN_EXPORT Data* Allocate(size_t minCapacity);
                    HWIN_EXPORT void Free(Data* data);
                };

                HWIN_EXPORT static DataManager dataManager;
                Data* data;


                void Release()
                {
                    if(data)
                    {
                        if(data->Release() == 0)
                        {
                            dataManager.Free(data);
                        }
                    }
                }
            public:
                StringBuilder()
                    : data(0)
                {}

                StringBuilder(size_t minCapacity)
                    : data(dataManager.Allocate(minCapacity))
                {}

                StringBuilder(const StringBuilder& other)
                    : data(other.data)
                {
                    if(data)
                    {
                        data->AddRef();
                    }
                }

                StringBuilder(StringBuilder&& other)
                    : data(other.data)
                {
                    other.data = nullptr;
                }

                ~StringBuilder()
                {
                    Release();
                }

                StringBuilder& operator = (const StringBuilder& other)
                {
                    if(data != other.data)
                    {
                        Release();
                        data = other.data;
                        if(data)
                        {
                            data->AddRef();
                        }
                    }
                    return *this;
                }

                StringBuilder& operator = (StringBuilder&& other)
                {
                    if(this != &other)
                    {
                        Release();
                        data = other.data;
                        other.data = nullptr;
                    }
                    return *this;
                }

                StringBuilder& Append(const wchar_t* text, size_t textLength)
                {
                    if(!data)
                    {
                        data = dataManager.Allocate(textLength);
                    }
                    data->Append(text,textLength);
                    return *this;
                }

                StringBuilder& Append(const String& s)
                {
                    auto length = s.length();
                    if(length)
                    {
                        if(!data)
                        {
                            data = dataManager.Allocate(length);
                        }
                        data->Append(s.c_str(),length);
                    }
                    return *this;
                }

                StringBuilder& Append(const wchar_t c)
                {
                    if(!data)
                    {
                        data = dataManager.Allocate(1);
                    }
                    data->Append(c);
                    return *this;
                }


                StringBuilder& operator += (const String& s)
                {
                    return Append(s);
                }

                StringBuilder& operator += (const wchar_t c)
                {
                    return Append(c);
                }


                String ToString() const
                {
                    if(data)
                    {
                        return data->ToString();
                    }
                    return String();
                }

            };


            


            class Endoding : public Object
            {
                unsigned codePage;
            public:
                typedef Object Base;

                HWIN_EXPORT static std::shared_ptr<Endoding> ASCII;
                HWIN_EXPORT static std::shared_ptr<Endoding> UTF8;
                HWIN_EXPORT static std::shared_ptr<Endoding> UTF7;
                HWIN_EXPORT static std::shared_ptr<Endoding> Latin1;

                HWIN_EXPORT Endoding();
                HWIN_EXPORT Endoding(unsigned theCodePage);

                HWIN_EXPORT String GetString( const std::vector<Byte::ValueType>& bytes );
                HWIN_EXPORT std::shared_ptr< std::vector<Byte::ValueType> > GetBytes( const String& theText );


            };

        };
    };
};


#endif // __HWINTEXT_H__


